# include "VideoDecoder.h"
extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavdevice/avdevice.h"
#include "libavutil/imgutils.h"
#include "libavutil/time.h"
#include "libswresample/swresample.h"
}
# include "libyuv.h"

void VideoDecoder::parse(char* _data, size_t& _size)
{
	AVCodecContext* video_encode_ctx = reinterpret_cast<AVCodecContext*>(_data);
	_size = sizeof(AVCodecContext);
	video_encode_ctx->extradata = reinterpret_cast<uint8_t*>(_data) + _size;
	_size += video_encode_ctx->extradata_size;
}

VideoDecoder::VideoDecoder(uint8_t* _video_encode_ctx)
{
	AVCodecContext* video_encode_ctx = reinterpret_cast<AVCodecContext*>(_video_encode_ctx);
	this->m_video_decode_codec = const_cast<AVCodec*>(avcodec_find_decoder(AV_CODEC_ID_H264));
	this->m_video_decode_codec_ctx = avcodec_alloc_context3(this->m_video_decode_codec);
	this->m_video_decode_codec_ctx->codec_id = AV_CODEC_ID_H264;
	this->m_video_decode_codec_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
	this->m_video_decode_codec_ctx->pix_fmt = video_encode_ctx->pix_fmt;
	this->m_video_decode_codec_ctx->bit_rate = video_encode_ctx->bit_rate;
	this->m_video_decode_codec_ctx->width = video_encode_ctx->width;
	this->m_video_decode_codec_ctx->height = video_encode_ctx->height;
	this->m_video_decode_codec_ctx->flags = video_encode_ctx->flags;
	if (video_encode_ctx->extradata != nullptr) {
		this->m_video_decode_codec_ctx->extradata_size = video_encode_ctx->extradata_size;
		this->m_video_decode_codec_ctx->extradata = reinterpret_cast<uint8_t*>(av_malloc(this->m_video_decode_codec_ctx->extradata_size));
		memcpy_s(this->m_video_decode_codec_ctx->extradata, this->m_video_decode_codec_ctx->extradata_size, video_encode_ctx->extradata, video_encode_ctx->extradata_size);
	}
	
	AVDictionary* output_param = nullptr;
	av_dict_set(&output_param, "preset", "veryfast", 0);
	av_dict_set(&output_param, "tune", "zerolatency", 0);
	int ret = avcodec_open2(this->m_video_decode_codec_ctx, this->m_video_decode_codec, &output_param);
	av_dict_free(&output_param);
	this->m_video_parser_ctx = av_parser_init(this->m_video_decode_codec_ctx->codec_id);

	this->m_video_decode_packet = av_packet_alloc();
	int buf_size = av_image_get_buffer_size(this->m_video_decode_codec_ctx->pix_fmt, this->m_video_decode_codec_ctx->width, this->m_video_decode_codec_ctx->height, 1);
	av_new_packet(this->m_video_decode_packet, buf_size);

	this->m_video_decode_frame = av_frame_alloc();
	this->m_video_decode_frame->pts = 0;
	this->m_video_decode_frame->width = this->m_video_decode_codec_ctx->width;
	this->m_video_decode_frame->height = this->m_video_decode_codec_ctx->height;
	this->m_video_decode_frame->format = this->m_video_decode_codec_ctx->pix_fmt;
}

void VideoDecoder::onData(const uint8_t* _data, int _size, uint8_t* _buffer, int _buffer_size)
{
	if (_data == nullptr) {
		return;
	}
	int ret = -1;
	int len = -1;
	while (_size > 0) {
		len = av_parser_parse2(this->m_video_parser_ctx, this->m_video_decode_codec_ctx,
			&this->m_video_decode_packet->data, &this->m_video_decode_packet->size, _data, _size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);
		_data += len;
		_size -= len;
		if (this->m_video_decode_packet->size == 0) {
			return;
		}
		ret = avcodec_send_packet(this->m_video_decode_codec_ctx, this->m_video_decode_packet);
		if (ret < 0) {
			return;
		}
		ret = avcodec_receive_frame(this->m_video_decode_codec_ctx, this->m_video_decode_frame);
		if (ret < 0) {
			return;
		}
		if (_buffer != nullptr) {
			libyuv::I420ToARGB(this->m_video_decode_frame->data[0], this->m_video_decode_frame->linesize[0],
				this->m_video_decode_frame->data[1], this->m_video_decode_frame->linesize[1],
				this->m_video_decode_frame->data[2], this->m_video_decode_frame->linesize[2],
				_buffer, this->m_video_decode_codec_ctx->width * 4,
				this->m_video_decode_codec_ctx->width, this->m_video_decode_codec_ctx->height);
		}
		av_packet_unref(this->m_video_decode_packet);
	}
}

VideoDecoder::~VideoDecoder()
{
	/*if (this->m_video_decode_codec_ctx != nullptr) {
		avcodec_send_frame(this->m_video_encode_codec_ctx, nullptr);
		if (this->m_video_encode_packet != nullptr) {
			while (avcodec_receive_packet(this->m_video_encode_codec_ctx, this->m_video_encode_packet) >= 0) {

			}
			av_packet_unref(this->m_video_encode_packet);
		}
	}*/

	if (this->m_video_decode_packet != nullptr) {
		av_packet_free(&this->m_video_decode_packet);
	}

	if (this->m_video_decode_frame != nullptr) {
		av_frame_free(&this->m_video_decode_frame);
	}

	if (this->m_video_decode_codec_ctx != nullptr) {
		avcodec_free_context(&this->m_video_decode_codec_ctx);
		this->m_video_decode_codec_ctx = nullptr;
	}
}
